package com.dataart.training.crimequalification.service;


import com.dataart.training.crimequalification.entity.UserEntity;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.authentication.AuthenticationCredentialsNotFoundException;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler;
import org.springframework.security.web.authentication.rememberme.PersistentTokenBasedRememberMeServices;
import org.springframework.security.web.context.HttpSessionSecurityContextRepository;
import org.springframework.stereotype.Service;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@Service
public class AuthenticationServiceImpl implements AuthenticationService {

    // ----------------------------------------------------------------------

    private static final Logger logger = LoggerFactory
            .getLogger(AuthenticationServiceImpl.class);

    private static final String NON_AUTHENTICATED_SPRING_SECURITY_ALIAS = "anonymousUser";

    // ----------------------------------------------------------------------

    /**
     * Performs programmatic authentication using Spring Security resources with given credentials.
     *
     * @param authenticationManager bean that processes an Authentication request.
     * @param username              username to attempt authentication with.
     * @param password              password to attempt authentication with.
     * @return <code>true</code> if authentication performed successfully, <code>false</code> otherwise.
     */

    public boolean doProgrammaticLogin(HttpServletRequest request,
                                       AuthenticationManager authenticationManager, String username, String password) {

        if (StringUtils.isEmpty(username) || StringUtils.isEmpty(password)) {
            throw new IllegalStateException("Username and password must not be empty");
        }

        try {
            Authentication auth = new UsernamePasswordAuthenticationToken(
                    username, password);
            Authentication result = authenticationManager.authenticate(auth);
            SecurityContext securityContext = SecurityContextHolder.getContext();
            securityContext.setAuthentication(result);
            request.getSession().setAttribute(HttpSessionSecurityContextRepository.
                    SPRING_SECURITY_CONTEXT_KEY, securityContext);
            return true;

        } catch (BadCredentialsException e) {
            return false;
        }
    }

    /**
     * Performs a check for an authentication, i.e., whether user is currently logged in.
     *
     * @return <code>true if user is authenticated</code>, <code>false</code> otherwise.
     */
    public boolean isAuthenticationPerformed() {
        Authentication auth = SecurityContextHolder.getContext().getAuthentication();
        return auth != null && !auth.getName().equals(NON_AUTHENTICATED_SPRING_SECURITY_ALIAS)
                && auth.isAuthenticated();
    }

    /**
     * Provides username of currently logged in user.
     *
     * @return username of currently logged in user, and empty string if no user is authenticated at the moment.
     */
    public String getLoggedInUserName() throws AuthenticationException {

        if (!isAuthenticationPerformed()) {
            throw new AuthenticationCredentialsNotFoundException("No authentication present.");
        }
        return SecurityContextHolder.getContext().getAuthentication().getName();

    }

    /**
     * Provides currently logged in user principal.
     *
     * @param userService user service class that allows to look up user by username in given storage.
     * @return user principal if user is authenticated.
     * @throws AuthenticationException if there is no authentication performed:
     * no Authentication object in the SecurityContext.
     */

    public UserEntity getCurrentLoggedInUser(UserService userService) throws AuthenticationException {

        try {
            return userService.getUserByUsername(getLoggedInUserName());
        } catch (AuthenticationException e) {
            throw new AuthenticationCredentialsNotFoundException("No user is authenticated at the moment.", e);
        }
    }

    /**
     * Performs programmatic request-and-response-based logout using Spring Security resources.
     *
     * @param request  http request associated with session
     * @param response http response associated with session
     */
    public void doProgrammaticLogout(HttpServletRequest request, HttpServletResponse response) throws Exception {

        Authentication auth = SecurityContextHolder.getContext().getAuthentication();
        if (auth != null) {
            new SecurityContextLogoutHandler().logout(request, response, auth);
            request.getSession().invalidate();
            new PersistentTokenBasedRememberMeServices().logout(request, response, auth);
        }
    }

}
